home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 May: Tool Chest / Developer CD Series May 1996 (Tool Chest) (Apple Computer) (1996).iso / Tool Chest / Sound / SoundApp / SoundUnit.c < prev    next >
Encoding:
Text File  |  1995-04-12  |  53.4 KB  |  1,641 lines  |  [TEXT/MMCC]

  1. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2. /*
  3. Apple Macintosh Developer Technical Support
  4.  
  5. SoundUnit
  6.  
  7. SoundUnit.c    - C Source
  8.  
  9. Versions:
  10.         1.03                January, 1990
  11.         1.04                Sept, 1990
  12.         1.2                    August, 1994        translated to C
  13.  
  14. Components:
  15.         SoundApp.c            January, 1990        MPW C source code
  16.         SoundUnit.c            January, 1990        MPW C source code
  17.         SoundUnit.h            January, 1990        MPW C source code
  18.         SoundApp.r            January, 1990        MPW Rez source code
  19.         SoundAppSnds.r        January, 1990        MPW Rez source code
  20.         SoundApp.make        January, 1990        MPW build script
  21.  
  22. Formatting was done with FONT = Courier or Monaco, SIZE = 10, TABS = 4
  23.  
  24. Version comments
  25.  
  26. 1.1:  This is the "new" SoundUnit which adds some new features.
  27.         • Some knowledge of the new Sound Manager is present
  28.           in areas that were work-arounds for old Sound Manager bugs
  29.         • Conversion to MPW 3.2 was established (with some amount of pain)
  30.         • Added the new Sound Manager error strings
  31.         • Checking of the sound header for supported encode values
  32.         • The amp value is ignored (never used) in a freqDurationCmd
  33.         • Added functions to test sound hardware/software features
  34.           such as stereo, MACE, Sound Input
  35.  
  36.  
  37. Formatting was done with FONT = Courier, SIZE = 10, TABS = 4
  38.  
  39.  
  40. SoundApp.c is a sample application source file for demonstrating the
  41. Sound Manager.  This portion of the source code handles the Sound Manager
  42. part of the application.  This source can be used by others.
  43.  
  44. Jim Reekes
  45. Sunday, August 7, 1994 7:06:41 PM
  46. */
  47. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  48.  
  49. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  50. //includes
  51. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  52.  
  53. #include <Errors.h>
  54. #include <FixMath.h>
  55. #include <fp.h>
  56. #include <GestaltEqu.h>
  57. #include <LowMem.h>
  58. #include <Memory.h>
  59. #include <MixedMode.h>
  60. #include <Resources.h>
  61.  
  62. #include <limits.h>
  63. #include <stddef.h>
  64.  
  65. #include "Sound.h"
  66. #include "SoundInput.h"
  67. #include "SoundUnit.h"
  68.  
  69. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  70. // private constants
  71. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  72.  
  73. enum {
  74.     kMaxChannels    = 4,                    //maximum number of supported channels
  75.  
  76.     kSoundComplete    = 0x1234                //flag for callBackCmd
  77. };
  78.  
  79. /*
  80. These are used as flags in the sound channel to determine the state
  81. of that channel.
  82. */
  83. enum {
  84.     kChanFreeState    = 0,                //channel is not in use
  85.     kChanCompleteState                    //channel has completed
  86. };
  87.  
  88. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  89. // macros
  90. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  91.  
  92. #if USESROUTINEDESCRIPTORS
  93. #define CreateRoutineDescriptor(info, proc)                                    \
  94.  RoutineDescriptor g##proc##RD = BUILD_ROUTINE_DESCRIPTOR(info, proc)
  95.  
  96. #define GetRoutineAddress(proc)    (&g##proc##RD)
  97.  
  98. #else
  99. #define GetRoutineAddress(proc)    proc
  100. #endif
  101.  
  102. // this belongs in LowMem.h
  103. extern unsigned short LMGetSoundActive( void )
  104.     TWOWORDINLINE( 0x1038, 0x027E ); /* MOVE.B $027E, D0 */
  105.  
  106. // For Power Macs, there is no Sound Driver and therefore SoundActive in
  107. // low memory should not be a problem. This is sometimes set by third parties
  108. // that are writting directly to the hardware, and also set by the old
  109. // sound driver when it is active. When this is happening, the Sound Manager
  110. // cannot work. So we check the low memory global to be less than 0. But
  111. // for Power Mac builds, we just return 0.
  112.  
  113. #if USESROUTINEDESCRIPTORS
  114. #define SoundDriverActive() false
  115. #else
  116. #define SoundDriverActive() (LMGetSoundActive() < 0)
  117. #endif
  118.  
  119. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  120. // private types
  121. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  122.  
  123. /*
  124. This is an element of a globals array that is used to keep track of
  125. sound channels created by the sound unit.  It contains all the useful
  126. information associated with the channel.  I keep the 'snd ' resource
  127. handle associated to this channel too.  This allows me to dispose of
  128. the data once the channel has completed its duties.
  129. */
  130.  
  131. struct ChanInfo {
  132.     SndChannelPtr    chan;
  133.     SndListHandle    dataHandle;
  134.     short            chanState;
  135.     short            chanType;
  136. };
  137. typedef struct ChanInfo ChanInfo;
  138. typedef ChanInfo *ChanInfoPtr;
  139.  
  140.  
  141. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  142. // private prototypes
  143. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  144.  
  145. void FreeChan(ChanInfoPtr info);
  146. OSErr ChanAvailable(ChanInfoPtr info);
  147. Boolean CompatibleChan(ChanInfoPtr info);
  148. OSErr InstallSampleSnd(ChanInfoPtr info, SndListHandle sndHandle);
  149. OSErr NewWaveChan(ChanInfoPtr info, short init);
  150.  
  151. pascal void DoCallBack(SndChannelPtr chan, SndCommand *theCmd);
  152. Boolean IsMyChan(SndChannelPtr chan);
  153. OSErr SndDataAvailable(SndListHandle sndHandle);
  154. ModRef GetSynthInfo(SndListHandle sndHandle);
  155. Boolean SupportedSH(SoundHeaderPtr sndPtr);
  156. OSErr ReleaseSynch(SndChannelPtr chan);
  157. OSErr Synch1Chan(SndChannelPtr chan, short count);
  158. OSErr SynchChans(SndChannelPtr chan1, SndChannelPtr chan2, SndChannelPtr chan3, SndChannelPtr chan4);
  159.  
  160.  
  161. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  162. //globals (The “g” prefix is used to emphasize that a variable is global.)
  163. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  164.  
  165. /*
  166. This is the global array of sound channel information.
  167. */
  168.  
  169.     ChanInfoPtr        gChanInfo;
  170.  
  171. /*
  172. gSoundMgrVersion is used to determine if the application is running
  173. with the old Sound Manager.  This was shipped prior to System 6.0.6.
  174. This flag is setup in the InitSoundUnit routine and used by the rest of this
  175. source file. There are workarounds to problems based on this condition.
  176. */
  177.     short            gSoundMgrVersion;
  178.  
  179. // allocate the RoutineDescriptors for Power Mac toolbox calls
  180. #if USESROUTINEDESCRIPTORS
  181. CreateRoutineDescriptor(uppSndCallBackProcInfo, DoCallBack);
  182. #endif
  183.  
  184. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  185. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  186. /*
  187. This is a dummy routine to allow the application to unload this segment.
  188. */
  189.  
  190. #pragma segment SoundUnit
  191. pascal void _SoundUnit(void)
  192. {
  193. }
  194.  
  195. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  196. /*
  197. Create storage for each of the four channels (4 * 1064 bytes) and
  198. initialize them.  If any of the channels cannot be allocated, return an
  199. error.  If the user only wants one channel, then we could just allocate
  200. one instead of four.  These channels are used at interrupt time.
  201.  
  202. VERSION 1.1: Added the new Sound Manager flag, gNewSndMgr.  First I have to
  203. test if the _SoundDispatch trap is available, since SndManagerVersion is
  204. a selector for _SoundDispatch.  This this trap isn't available, then calling
  205. SndManagerVersion would result in an unimplemented instruction.  If an error
  206. is returned, it is the old Sound Manager.  If it is zero, then the call
  207. is available (via-MIDI Mgr?) but it isn't the new Sound Manager.  Greater
  208. than zero means it is the new Sound Manager.
  209.  
  210. VERSION 1.2: Only supports Sound Manager 2.0 or later. I would personally
  211. require version 3.0 or later in my own products.
  212. */
  213.  
  214. #pragma segment Initialize
  215. pascal OSErr InitSoundUnit(void)
  216. {
  217.     OSErr        theErr;
  218.  
  219.     // check if the supported Sound Manager is present, this is the one
  220.     // that has Sound Input and supports the _SoundDispatch trap
  221.  
  222.     theErr = noErr;
  223.     gSoundMgrVersion = GetSoundMgrVersion();
  224.     if (gSoundMgrVersion > 1)
  225.     {
  226.         gChanInfo = (ChanInfoPtr)NewPtrClear(sizeof(ChanInfo[kMaxChannels]));
  227.         if (gChanInfo == nil)
  228.             theErr = MemError();
  229.     }
  230.     return(theErr);
  231. }
  232.  
  233. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  234. /*
  235. To determine if MACE is available, there are two case.  If the new Sound Manager
  236. is running then MACE may be built in.  This is easy enough to test for by calling
  237. the new trap call.  Otherwise I have to test for the presence of the MACE
  238. snth resources.  The old MACE snths could only be present if the user ran the
  239. MACE Installer Scripts which are available from APDA. This was only supported
  240. on some versions of System 6.0.x.
  241.  
  242. VERSION 1.2: Forget about dealing with MACE prior to Sound Manager 2, it was
  243. just a hack and didn't work on all machines.
  244. */
  245.  
  246. #pragma segment Main
  247. pascal Boolean HasMACE(void)
  248. {
  249.     NumVersion version;
  250.     Boolean result;
  251.  
  252.     result = false;
  253.     if (GetSoundMgrVersion() > 1)
  254.     {
  255.         version = MACEVersion();
  256.         result = (version.majorRev > 0);            //is the built-in MACE here?
  257.     }
  258.     return (result);
  259. }
  260.  
  261. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  262. /*
  263. This is used to determine if Sound Input is available. The Gestalt flag
  264. gestaltHasSoundInputDevice can be used to determine if an input device is
  265. available. This flag did not exist prior to System 7. The other related
  266. flag gestaltBuiltInSoundInput can only be used to determine if the machine
  267. has built-in sound input hardware, so don't be mislead. In System 6.0.7 you
  268. would have to use SPBGetIndexdDevice to find the fist one.  If this returns
  269. noErr then Sound Input is available. Also, the icon handle returned by this
  270. call has to be disposed of by you the caller.
  271.  
  272. VERSION 1.1:  This is the external routine for users to determine if
  273. sound input is available.
  274.  
  275. VERSION 1.2: Use the Gestalt method since we know that we're running Sound
  276. Manager 2 or later which supports the gestaltHasSoundInputDevice flag.
  277. */
  278.  
  279. #pragma segment Main
  280. pascal Boolean HasSoundInput(void)
  281. {
  282.     long        response;
  283.     OSErr        theErr;
  284.     Boolean        result;
  285.  
  286.     theErr = Gestalt(gestaltSoundAttr, &response);
  287.     if ( (theErr == noErr) && (response & (1<<gestaltHasSoundInputDevice)) )
  288.         result = true;
  289.     else
  290.         result = false;
  291.  
  292.     return (result);
  293. }
  294.  
  295. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  296. /*
  297. VERSION 1.1:  This is the external routine for users to determine if
  298. sound input is available. Sound Manager 3.0 can always support stereo,
  299. even on mono hardware.
  300. */
  301.  
  302. #pragma segment Main
  303. pascal Boolean HasStereoSupport(void)
  304. {
  305.     long response;
  306.     OSErr theErr;
  307.     Boolean result;
  308.  
  309.     theErr = Gestalt(gestaltSoundAttr, &response);
  310.     if ( (theErr == noErr) && (response & (1<<gestaltStereoCapability)) )
  311.         result = true;
  312.     else
  313.         result = false;
  314.     return (result);
  315. }
  316.  
  317. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  318. /*
  319. This will return the version of the sound manager that is currently running.
  320. A special case is necessary because the first sound manager that reported
  321. its version didn't have the SndSoundManagerVersion() implemented. So that means
  322. older versions are really 1.0, and the first oldest version returned by
  323. SndSoundManagerVersion is 2.0. Anything older than 2.0 has a few problems.
  324. */
  325.  
  326. pascal short GetSoundMgrVersion(void)
  327. {
  328.     NumVersion version;
  329.     long response;
  330.     short result;
  331.     OSErr theErr;
  332.  
  333.     result = 1;
  334.     theErr = Gestalt(gestaltSoundAttr, &response);
  335.     if ( (theErr == noErr) && (response & (1<<gestaltSoundIOMgrPresent)) )
  336.     {
  337.         version = SndSoundManagerVersion();
  338.         result = version.majorRev;
  339.     }
  340.     return(result);
  341. }
  342.  
  343. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  344. /*
  345. This routine can be called to determine if the sound has completed.  When
  346. this is true, the data can be disposed of.  It is set by the sound
  347. channel's completion routine.  It is placed in the Main segment because
  348. it is called by the event loop.
  349. */
  350.  
  351. #pragma segment Main
  352. pascal Boolean HasSoundCompleted(void)
  353. {
  354.     short        i;
  355.     Boolean        result;
  356.  
  357.     result = true;                            //assume true, then check for busy channels
  358.     for (i = 0; i < kMaxChannels; i++)
  359.     {
  360.         // if we have a channel and it is not completed, then we're still busy playing
  361.         if ((gChanInfo[i].chan != nil) && (gChanInfo[i].chanState != kChanCompleteState))
  362.         {
  363.             result = false;
  364.             break;
  365.         }
  366.     }
  367.     return(result);
  368. }
  369.  
  370. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  371. /*
  372. This routine can be called at any time.  It will return true when the
  373. SoundUnit has an open channel.  This can be can considered the same as
  374. sound being active.  As long as the channel is open, no other channels can
  375. be opened.  It is placed in the Main segment because it is called by the
  376. event loop.
  377. */
  378.  
  379. #pragma segment Main
  380. pascal Boolean HasChannelOpen(void)
  381. {
  382.     short        i;
  383.     Boolean        result;
  384.  
  385.     result = false;
  386.     for (i = 0; i < kMaxChannels; i++)
  387.     {
  388.         if (gChanInfo[i].chan != nil)
  389.         {
  390.             result = true;
  391.             break;
  392.         }
  393.     }
  394.  
  395.     return(result);
  396. }
  397.  
  398. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  399. /*
  400. Given a channel and timbre(sounds like "tom burr"), this will adjust the
  401. tone quality of the square wave synthesizer.  Changing the tone can only be done
  402. before playing a square wave.  On a Mac with the Apple Sound Chip, this can be
  403. done in real time while a note is playing.  But, since there's no
  404. supported method for determining if the ASC is available I have to assume
  405. that it's not.  I use the immediate flag to determine if the user wants to
  406. change the timbre now, or queue the command.  If the queue is full, it
  407. will wait for the command to be accepted.
  408.  
  409. BUG NOTE: There is a bug in the Sound Manager running on the Mac Plus or
  410. SE where sending a timbreCmd with a timbre of 255(a legal value)will
  411. crash.  The difference between 254 and 255 isn't audible, so I only allow
  412. a maximum of 254 in any case.
  413. */
  414.  
  415. #pragma segment SoundUnit
  416. pascal OSErr SetSquareWaveTimbre(SndChannelPtr squareChan, short timbre, Boolean immediate)
  417. {
  418.     SndCommand theCmd;
  419.     OSErr      result;
  420.  
  421.     if (timbre > 254)
  422.         timbre = 254;
  423.  
  424.     theCmd.cmd = timbreCmd;
  425.     theCmd.param1 = timbre;
  426.     theCmd.param2 = 0;
  427.  
  428.     if (immediate)
  429.         result = SndDoImmediate(squareChan, &theCmd);
  430.     else
  431.         result = SndDoCommand(squareChan, &theCmd, kWait);
  432.  
  433.     return(result);
  434. }
  435.  
  436. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  437. /*
  438. Given a channel and note information, this will place the note into the
  439. channel's queue.  The note contains the amplitude and note value.  It is a
  440. four byte parameter with the high byte containing the amplitude.  I use
  441. SndDoCommand with the noWait flag set to wait for the channel to except
  442. the command in the case the queue is currently full.
  443. */
  444.  
  445. #pragma segment SoundUnit
  446. pascal OSErr SendNote(SndChannelPtr chan, short duration, long note)
  447. {
  448.     SndCommand theCmd;
  449.  
  450.     theCmd.cmd = freqDurationCmd;
  451.     theCmd.param1 = duration;
  452.     theCmd.param2 = note;
  453.  
  454.     return(SndDoCommand(chan, &theCmd, kWait));
  455. }
  456.  
  457. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  458. /*
  459. Given a channel, this will place a quietCmd into the channel's queue.  I
  460. use SndDoCommand with the noWait flag set to wait for the channel to
  461. except the command in the case it is currently full.
  462.  
  463. BUG NOTE: A sequence of notes and rests will not work unless quietCmds
  464. are between them.  Rests have to be made quiet before they rest, if that
  465. makes any more sense.  A freqDurationCmd will loop, causing the sound in progress
  466. to continue, until a quietCmd is received.
  467. */
  468.  
  469. #pragma segment SoundUnit
  470. pascal OSErr SendQuiet(SndChannelPtr chan, Boolean immediate)
  471. {
  472.     SndCommand theCmd;
  473.     OSErr      result;
  474.  
  475.     theCmd.cmd = quietCmd;
  476.     theCmd.param1 = 0;
  477.     theCmd.param2 = 0;
  478.  
  479.     if (immediate)
  480.         result = SndDoImmediate(chan, &theCmd);
  481.     else
  482.         result = SndDoCommand(chan, &theCmd, kWait);
  483.  
  484.     return(result);
  485. }
  486.  
  487. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  488. /*
  489. Given a channel and duration, this will place the rest into the channel's
  490. queue.  Before sending a rest a quietCmd is needed.  Rests don't work
  491. unless you tell the Sound Manager to be quiet too.  I use SndDoCommand
  492. with the noWait flag set to wait for the channel to except the command in
  493. the case it is currently full.
  494. */
  495.  
  496. #pragma segment SoundUnit
  497. pascal OSErr SendRest(SndChannelPtr chan, short duration)
  498. {
  499.     SndCommand theCmd;
  500.     OSErr      theErr;
  501.  
  502.     theErr = SendQuiet(chan, kWait);
  503.  
  504.     if (theErr == noErr) {
  505.         theCmd.cmd = restCmd;
  506.         theCmd.param1 = duration;
  507.         theCmd.param2 = 0;
  508.  
  509.         theErr = SndDoCommand(chan, &theCmd, kWait);
  510.     }
  511.  
  512.     return(theErr);
  513. }
  514.  
  515. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  516. /*
  517. Test if the channel is free, and if not then call SndDisposeChannel.
  518. This will release the synthesizer(snth resource)code and the
  519. required hardware.  If we didn't do this, no other channels would
  520. work.  I also test myChan for having a snd resource attached to the
  521. channel.  If so, then I mark it as purgeable and reset the data to nil.
  522.  
  523. BUG NOTE: Calling SndDisposeChannel while or immediately after playing
  524. a sequence of notes would often hang/crash a non-Apple Sound Chip based Mac.
  525. Issuing a quietCmd first kept the Sound Manager happy and my Mac from
  526. crashing.
  527. */
  528.  
  529. #pragma segment SoundUnit
  530. void FreeChan(ChanInfoPtr info)
  531. {
  532.     OSErr      theErr;
  533.  
  534.     if (info->chan != nil) {
  535.         theErr = SendQuiet(info->chan, !kWait);  // ignore error
  536.         theErr = SndDisposeChannel(info->chan, !kWait);
  537.         info->chan = nil;
  538.         info->chanState = kChanFreeState;
  539.     }
  540.  
  541.     if (info->dataHandle != nil) {
  542.         HUnlock((Handle)info->dataHandle);
  543.         HPurge((Handle)info->dataHandle);
  544.         info->dataHandle = nil;
  545.     }
  546. }
  547.  
  548. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  549. /*
  550. This routine is called by an application that established a sound to be
  551. played asynchronously.  This is in effect, the routine to be used after
  552. the completion routine has been called.  The application should call this
  553. once HasSoundCompleted() returns true.  In the case the application is using
  554. multiple channels, we will only free a channel once it has been marked as
  555. kChanCompleteState.
  556. */
  557.  
  558. #pragma segment SoundUnit
  559. pascal void DoSoundComplete(void)
  560. {
  561.     short        i;
  562.  
  563.     for (i = 0; i < kMaxChannels; i++)
  564.     {
  565.         if (gChanInfo[i].chanState != kChanFreeState)
  566.             FreeChan(&gChanInfo[i]);
  567.     }
  568. }
  569.  
  570. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  571. /*
  572. This is the routine that will force all channels to be released  This is used by
  573. all routines just before opening a new channel to force all channels to be disposed.
  574. */
  575.  
  576. #pragma segment SoundUnit
  577. pascal void FreeAllChans(void)
  578. {
  579.     short        i;
  580.  
  581.     for (i = 0; i < kMaxChannels; i++)
  582.         FreeChan(&gChanInfo[i]);
  583. }
  584.  
  585. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  586. /*
  587. This is the final routine to be called by the application when it is has
  588. finished using this SoundUnit.  This will dispose of all the channels and
  589. memory used by this SoundUnit.
  590. */
  591.  
  592. #pragma segment SoundUnit
  593. pascal void FreeSoundUnit(void)
  594. {
  595.     FreeAllChans();
  596.     DisposPtr((Ptr)gChanInfo);
  597. }
  598.  
  599. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  600. /*
  601. This will be called at interrupt time by the Sound Manager when it
  602. receives a callBackCmd.  I use the second parameter of the command to hold
  603. my application's A5 reference.  I first set up A5 so that I can access my
  604. globals.  I mark the given channel as being complete.  This lets the
  605. application know that the callBackCmd has been processed.  The callBackCmd
  606. can be used for other purposes, and the first parameter of the command
  607. could be a flag to a more extensive routine.  Synchronizing the application
  608. with the channel is possible with this method.
  609.  
  610. WARNING: This routine MUST be resident in memory and cannot make a call
  611. to a non-resident segment.  I put this into the Main segment because of
  612. this.
  613.  
  614. BUG NOTE: System 6.0.4 has a bug in _SndPlay when using a sampled sound
  615. 'snd '.  A bogus callBackCmd is placed into the queue immediately after
  616. the bufferCmd used to play the sound.  This bogus callBackCmd will cause
  617. my callBackProc to be called when I wasn't expecting it.  I have been
  618. using the command's second parameter to contain my A5 address.  If I'm
  619. given a bogus callBackCmd, it would be really bad to set A5 address to
  620. this bogus parameter in the command.  I found that the bogus callBackCmd
  621. contains the handle to the 'snd ' passed in to _SndPlay.  I also found
  622. that param1 contains the handle's state bits(results of HGetState).  To
  623. work with this bug I set my real callBackCmd's param1 to a specific value
  624. when I installed it into the queue.  See the SoundComplete routine.  Then
  625. I test the callBackCmd to make sure I'm dealing with the real one.
  626.  
  627. VERSION 1.2: No longer using A5 globals.  Get the address that we need
  628. in param2, instead of passing in our application's A5 address.
  629. */
  630.  
  631. #pragma segment Main
  632. pascal void DoCallBack(SndChannelPtr chan, SndCommand *theCmd)
  633. {
  634. #pragma unused (chan)
  635.     ChanInfoPtr        info;
  636.  
  637.     if (theCmd->param1 == kSoundComplete)         // if it's my callBackCmd
  638.     {
  639.         info = (ChanInfoPtr)theCmd->param2;
  640.         info->chanState = kChanCompleteState;    // this channel is done
  641.     }
  642. }
  643.  
  644. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  645. /*
  646. I use this to install the callBackCmd into the given channel.  I need to
  647. pass in our A5 along with the command so that the callBack routine can
  648. access my globals.  I also wait until the channel is ready for another
  649. command in the case of the channel being full.  Once the Sound Manager
  650. calls my call back procedure I will dispose of the channel.  So, this is
  651. the last sound command to be sent to a channel.  I pass to the call back
  652. A5 in the second parameter of the callBackCmd.  Refer to Tech Note #208.
  653.  
  654. VERSION 1.2: No longer using A5 globals.  Get the address that we need
  655. in param2, instead of passing in our application's A5 address.
  656. */
  657.  
  658. #pragma segment SoundUnit
  659. pascal OSErr SoundComplete(SndChannelPtr chan)
  660. {
  661.     SndCommand    theCmd;
  662.     OSErr        result;
  663.     short        i;
  664.  
  665.     theCmd.cmd = callBackCmd;
  666.     theCmd.param1 = kSoundComplete;
  667.     theCmd.param2 = 0;                            // initialize value
  668.  
  669.     for (i = 0; i < kMaxChannels; i++)
  670.     {
  671.         if (gChanInfo[i].chan == chan)
  672.         {
  673.             theCmd.param2 = (long)(&gChanInfo[i]);
  674.             break;
  675.         }
  676.     }
  677.     if (theCmd.param2 == 0)
  678.         result = badChannel;                    // channel wasn't one of ours
  679.     else
  680.         result = SndDoCommand(chan, &theCmd, kWait);
  681.  
  682.     return(result);
  683. }
  684.  
  685. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  686. /*
  687. This routine will test the given channel to see if it will really produce
  688. sound.  The Sound Manager in System 6.0x will return noErr even if the
  689. channel isn't going to work.  In the future the Sound Manager will return
  690. the proper error.  Until then, I use this routine to determine this for me.
  691. There can only be a single channel at any time, unless I have the wave
  692. table synthesizer open.  This will allow four channels.  Channels have
  693. a pointer to the next channel, and if this is not nil I suspect the
  694. given channel will not work.  I test the given channel for being a
  695. wave type, and if so I need to see if the other channels I've got are
  696. also wave type.  If it doesn't look like the channels is available, I
  697. return badChannel.
  698.  
  699. This routine assumes the channel passed in is the first channel allocated.
  700. Channels are held in a linked list, and the first one to be tested has
  701. to be the first one allocated.
  702.  
  703. BUG NOTE: If an application is not using the Sound Manager and instead
  704. uses the older Sound Driver, any given channel will fail.  Or if the other
  705. application does not release is channels, then my channels will not work.
  706. The most noticeable offender of this is HyperCard.  Friendly applications
  707. will dispose of their channels at suspend/resume times or ASAP.
  708.  
  709. VERSION 1.1: Added the new Sound Manager test.  The new Sound Manager will
  710. allow multiple sound channels, and returns proper error codes.
  711.  
  712. VERSION 1.2: This is not used since we only support Sound Manager 2.0 or later.
  713. */
  714.  
  715. #pragma segment SoundUnit
  716. OSErr ChanAvailable(ChanInfoPtr info)
  717. {
  718.     OSErr        result;
  719.     short        i;
  720.  
  721.     if (gSoundMgrVersion >= 3)
  722.         return(noErr);
  723.  
  724.     result = noErr;
  725.  
  726.     if (SoundDriverActive())                        // check for sound driver
  727.         return(notEnoughHardwareErr);
  728.  
  729.     if (info->chan->nextChan != nil)                // looks bad
  730.     {
  731.         result = badChannel;                          // prepart to fail
  732.         if (info->chanType == waveTableSynth)        // last attempt
  733.         {
  734.             if (IsMyChan(info->chan->nextChan))
  735.             {
  736.                 for (i = 0; i < kMaxChannels; i++)
  737.                 {
  738.                     if (! (CompatibleChan(&gChanInfo[i])))
  739.                         break;
  740.                     result = noErr;                  // got lucky
  741.                 }
  742.             }
  743.         }
  744.     }
  745.     return(result);
  746. }
  747.  
  748. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  749. /*
  750. This is a utility routine that works with ChanAvailable.  It checks to
  751. see if the given channel is one of the four channels we opened.  If it
  752. is, IsMyChan returns true.  If it isn't, false is returned.
  753. */
  754.  
  755. #pragma segment SoundUnit
  756. Boolean IsMyChan(SndChannelPtr chan)
  757. {
  758.     short        i;
  759.     Boolean        result;
  760.  
  761.     result = false;
  762.     for (i = 0; i < kMaxChannels; i++)
  763.     {
  764.         if (gChanInfo[i].chan == chan)
  765.         {
  766.             result = true;
  767.             break;
  768.         }
  769.     }
  770.     return(result);
  771. }
  772.  
  773. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  774. /*
  775. This is a utility routine that works with ChanAvailable.  It returns
  776. true if the given channel is either a wave type of channel.  It also
  777. returns true if the channel is free.  Otherwise, it returns false.
  778. */
  779.  
  780. #pragma segment SoundUnit
  781. Boolean CompatibleChan(ChanInfoPtr info)
  782. {
  783.     Boolean        result;
  784.  
  785.     if (    (info->chanType == waveTableSynth)        // wave or..
  786.         ||    (info->chanState == kChanFreeState) )    // free chan
  787.  
  788.         result = true;
  789.     else
  790.         result = false;
  791.  
  792.     return(result);
  793. }
  794.  
  795. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  796. /*
  797. Given a resource handle, this will attempt to load it into memory.  If the
  798. data is not available, then return an error.
  799. */
  800.  
  801. #pragma segment SoundUnit
  802. OSErr SndDataAvailable(SndListHandle sndHandle)
  803. {
  804.     OSErr result;
  805.  
  806.     result = noErr;
  807.     if (sndHandle != nil) {
  808.         LoadResource((Handle)sndHandle);
  809.         if (*sndHandle == nil)
  810.             result = nilHandleErr;            // master pointer is nil
  811.     }
  812.     else
  813.         result = nilHandleErr;                // user passed a nil handle
  814.     return(result);
  815. }
  816.  
  817. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  818. /*
  819. This is used to put the given sound resource into memory and hold it there.
  820. I also use a MoveHHi to keep the heap from being fragmented.  If this
  821. fails, then I return an error.  I dereference the handle and check if the
  822. master pointer is nil.  This would mean the data could not be loaded.
  823. */
  824.  
  825. #pragma segment SoundUnit
  826. pascal OSErr HoldSnd(SndListHandle sndHandle)
  827. {
  828.     OSErr result;
  829.  
  830.     if (sndHandle != nil) {
  831.         LoadResource((Handle)sndHandle);
  832.         if (*sndHandle == nil)
  833.             result = nilHandleErr;            // master pointer is nil
  834.         else {
  835.             result = noErr;
  836.             HLockHi((Handle)sndHandle);
  837.         }
  838.     }
  839.     else
  840.         result = nilHandleErr;                // user passed a nil handle
  841.     return(result);
  842. }
  843.  
  844. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  845. /*
  846. This routine will return the 'snth' resource ID specified by the sound.
  847. I use this to determine if the given sound will work with _SndPlay.
  848. This routine does not require the data to be in memory when called.  It
  849. also doesn't lock it down while looking for the information.
  850.  
  851. VERSION 1.1:  If no synth information is found in the snd then by default
  852. it is assumed to be for the squareWaveSynth.
  853. */
  854.  
  855. #pragma segment SoundUnit
  856. ModRef GetSynthInfo(SndListHandle sndHandle)
  857. {
  858.     SndListPtr        soundPtr;
  859.     OSErr            theErr;
  860.     ModRef            currSynth;
  861.  
  862.     currSynth.modNumber = kNoSynth;            //initialize to no synth, and no init
  863.     currSynth.modInit = 0;
  864.     theErr = SndDataAvailable(sndHandle);
  865.     if (theErr == noErr)
  866.     {
  867.         soundPtr = (*sndHandle);
  868.         if (soundPtr->format == firstSoundFormat)
  869.         {
  870.             if (soundPtr->numModifiers != 0)
  871.                 currSynth = soundPtr->modifierPart[0];
  872.             else
  873.                 currSynth.modNumber = squareWaveSynth;
  874.         }
  875.         else                                //snd is a format 2 for HyperCard
  876.             currSynth.modNumber = sampledSynth;
  877.     }
  878.     return(currSynth);
  879. }
  880.  
  881. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  882. /*
  883. This routine will cruise through the given snd resource  It will locate
  884. the sound data, if any, and return its type and offset into the resource.
  885. I prefer to return an offset instead of a pointer because I don't want
  886. to have the data locked in memory.  If I return an offset, the caller
  887. can decided when and if it wants the resource locked down to access the
  888. sound data.   The first step in finding this data is to determine if I'm
  889. looking at a format 1 or 2 type snd.  A type 2 is easy, but a type 1 will
  890. require me to find the number of snths specified and then to skip over
  891. each one including the init option.  Once this is done, I have a pointer
  892. to the number of commands in the snd.  When I've found the first one, I
  893. examine it to find out if it is a sound data command.  Being it's a sound
  894. resource, the command will also have its dataPointerFlag set.  Once I've
  895. found a command I'm looking for I return its type and offset, then get out
  896. of the do-while block.  Otherwise I go on to the next command.  All of this
  897. makes it possible to get the sound data for use as an instrument sound.
  898. Typically this will be a sampled sound.
  899. */
  900.  
  901. #pragma segment SoundUnit
  902. pascal long GetSndDataOffset(SndListHandle sndHandle, short *dataType, short *waveLength)
  903. {
  904.     Ptr                cruisePtr;
  905.     long            sndDataOffset;
  906.     short            synths;
  907.     short            howManyCmds;
  908.  
  909.     sndDataOffset = 0;                            // initialize to defaults
  910.     *dataType = kNoSynth;
  911.     *waveLength = 0;
  912.     if (sndHandle == nil)
  913.         return (sndDataOffset);                    // return no data
  914.  
  915.     if (*sndHandle != nil) {
  916.         if ((**sndHandle).format == firstSoundFormat) {
  917.             synths = (**sndHandle).numModifiers;
  918.             cruisePtr = (Ptr)&(**sndHandle).modifierPart;
  919.             cruisePtr += (sizeof(ModRef) * synths);
  920.         }
  921.         else
  922.             cruisePtr = (Ptr)&((**(Snd2ListHandle)sndHandle).numCommands);
  923.         howManyCmds = *(short *)cruisePtr;        // pointing at number of cmds
  924.         cruisePtr += sizeof(howManyCmds);
  925.  
  926.         // cruisePtr is now at the first sound command
  927.         // cruise all commands and find a soundCmd or bufferCmd
  928.         do {
  929.             switch (((SndCmdPtr)cruisePtr)->cmd) {
  930.  
  931.                 case soundCmd | dataOffsetFlag:
  932.                 case bufferCmd | dataOffsetFlag:
  933.                     *dataType = sampledSynth;
  934.                     sndDataOffset = ((SndCmdPtr)cruisePtr)->param2;
  935.                     howManyCmds = 0;            // done, get out of loop
  936.                     break;
  937.  
  938.                 case waveTableCmd | dataOffsetFlag:
  939.                     *dataType = waveTableSynth;
  940.                     *waveLength = ((SndCmdPtr)cruisePtr)->param1;
  941.                     sndDataOffset = ((SndCmdPtr)cruisePtr)->param2;
  942.                     howManyCmds = 0;            // done, get out of loop
  943.                     break;
  944.  
  945.                 default:                        // catch any other type of cmd
  946.                     cruisePtr += sizeof(SndCommand);
  947.                     howManyCmds -= 1;
  948.                     break;
  949.             }
  950.         } while (howManyCmds >= 1);                // done with all the commands
  951.     }
  952.     return(sndDataOffset);
  953. }
  954.  
  955. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  956. /*
  957. VERSION 1.1:  Check the given sound header as being supported by the
  958. running Sound Manager.  The encode fields of the header are tested.
  959. The standard encode always works.  The MACE compressed sound will only work if
  960. MACE is present.  A MACE sound can also be a stereo sound, which will only
  961. work on stereo hardware.  The expanded sound is for a stereo sound and/or
  962. 16bit samples and is only supported by Sound Manager 2.0 or later.
  963. If the sound is a stereo sound, then it requires stereo support.
  964.  
  965. VERSION 1.2: Support for Sound Manager 3 and beyond. Any compressed
  966. sound may work if Sound Manager 3.0 or later is present.
  967. */
  968.  
  969. #pragma segment SoundUnit
  970. Boolean SupportedSH(SoundHeaderPtr sndPtr)
  971. {
  972.     Boolean result;
  973.  
  974.     result = false;
  975.     switch (sndPtr->encode)
  976.     {
  977.  
  978.         case stdSH:
  979.             result = true;
  980.             break;
  981.  
  982.         case cmpSH:
  983.             if (gSoundMgrVersion < 3)
  984.             {
  985.                 //Sound Manager 2.0 will only support MACE compressed sound
  986.                 //and only stereo sound on stereo machines.
  987.  
  988.                 if (    (((CmpSoundHeaderPtr)sndPtr)->snthID == MACE3snthID)
  989.                      || (((CmpSoundHeaderPtr)sndPtr)->snthID == MACE6snthID) )
  990.                 {
  991.                     if (((CmpSoundHeaderPtr)sndPtr)->numChannels == 1)
  992.                         result = true;
  993.                     else
  994.                         result = HasStereoSupport();
  995.                 }
  996.             }
  997.             else
  998.                 result = true;                //Sound Manager 3 and later does it all
  999.             break;
  1000.  
  1001.         case extSH:            // first check for 8 bit sounds
  1002.             if (gSoundMgrVersion < 3)
  1003.             {
  1004.  
  1005.                 if (((ExtSoundHeaderPtr)sndPtr)->sampleSize == 8)
  1006.                 {
  1007.                     if (((ExtSoundHeaderPtr)sndPtr)->numChannels == 1)
  1008.                         result = true;                    // it's a mono sound, no problem
  1009.                     else
  1010.                          result = HasStereoSupport();    // only if we have stereo
  1011.                  }
  1012.                  else
  1013.                      result = false;                        // non-8 bit not allowed
  1014.             }
  1015.             else        // must be a 16 bit sound, Sound Manager 3 and later does it all
  1016.                 result = true;
  1017.             break;
  1018.  
  1019.     }
  1020.     return (result);
  1021. }
  1022.  
  1023. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1024. /*
  1025. Given a channel and sampled sound resource, this routine will install the
  1026. sound into the channel for use as an instrument.  This allows an
  1027. application to send freqDurationCmds to the channel and play a melody.  If I sent
  1028. a bufferCmd instead of the soundCmd, the Sound Manager would play the
  1029. sampled sound.  This is basically what _SndPlay would do with a format 2
  1030. snd.  I insure that I am using only the proper buffer format having the
  1031. standard encode option.  If I were to support compressed sounds, I would
  1032. have to call the MACE synthesizers to expand the buffer before I can use
  1033. it as an instrument.  If I don't get a sampled sound of standard encoding
  1034. I'll return a bad format error.  I use _SndDoImmediate to get the sound
  1035. installed because I don't want this command to be queued.
  1036.  
  1037. VERSION 1.1:  Check for a supported sound header.
  1038. */
  1039.  
  1040. OSErr InstallSampleSnd(ChanInfoPtr info, SndListHandle sndHandle)
  1041. {
  1042.     SndCommand        theCmd;
  1043.     SoundHeaderPtr    dataPtr;
  1044.     long            dataOffset;
  1045.     short            sndDataType;
  1046.     short            ignore;
  1047.     OSErr            theErr;
  1048.  
  1049.     theErr = HoldSnd(sndHandle);
  1050.     if (theErr == noErr) {
  1051.         dataOffset = GetSndDataOffset(sndHandle, &sndDataType, &ignore);
  1052.         if (sndDataType == sampledSynth) {
  1053.             dataPtr = (SoundHeaderPtr)((long)(*sndHandle) + dataOffset);
  1054.             if (stdSH == dataPtr->encode) {
  1055.                 theCmd.cmd = soundCmd;
  1056.                 theCmd.param1 = 0;
  1057.                 theCmd.param2 = (long)dataPtr;
  1058.                 info->dataHandle = sndHandle;
  1059.                 theErr = SndDoImmediate(info->chan, &theCmd);
  1060.             } else
  1061.                 theErr = badFormat;                            //return a bad format error
  1062.         } else
  1063.             theErr = badFormat;                                //return a bad format error
  1064.         if (theErr != noErr) {
  1065.             HUnlock((Handle)sndHandle);                        //and free up the resource
  1066.             HPurge((Handle)sndHandle);
  1067.         }
  1068.     }
  1069.     return (theErr);
  1070. }
  1071.  
  1072. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1073. /*
  1074. This routine will create a sampled sound channel using the INIT option
  1075. given.  Typically this will be 0.  In any case with System 6.0x this
  1076. option is ignored by the sampled sound synthesizer.  The given sound
  1077. resource will be installed into the channel for use as an instrument.
  1078.  
  1079. WARNING: If the application does not want an instrument sound, then the
  1080. sndInstrument handle MUST be passed in as nil.
  1081.  
  1082. BUG NOTE: The sampled sound synthesizer in System 6.0x does not check for
  1083. a Memory Manager error when allocating its internal buffer.  There is a
  1084. call to NewPtr(1316)and if a nil is returned, the Sound Manager will
  1085. write randomly to low memory.  This can occur when calling _SysBeep under
  1086. low memory conditions.  Also, this pointer is allocated into the
  1087. application's heap instead of the system's.
  1088.  
  1089. VERSION 1.2: We no longer need to call ChanAvailable since we're only
  1090. supporting Sound Manager 2.0 or later.
  1091. */
  1092.  
  1093. #pragma segment SoundUnit
  1094. pascal OSErr GetSampleChan(SndChannelPtr *sampleChan, long init, SndListHandle sndInstrument)
  1095. {
  1096.     OSErr theErr;
  1097.  
  1098.     FreeAllChans();
  1099.     theErr = SndNewChannel(&(gChanInfo[0].chan), sampledSynth,
  1100.                             init, GetRoutineAddress(DoCallBack));
  1101.     if (theErr == noErr) {
  1102.         gChanInfo[0].chanType = sampledSynth;
  1103.         theErr = InstallSampleSnd(&gChanInfo[0], sndInstrument);
  1104.     }
  1105.     if (theErr != noErr)
  1106.         FreeAllChans();
  1107.     *sampleChan = gChanInfo[0].chan;
  1108.     return (theErr);
  1109. }
  1110.  
  1111. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1112. /*
  1113. Only supported by Sound Manager 2.0 and later
  1114.  
  1115. */
  1116.  
  1117. #pragma segment SoundUnit
  1118. pascal OSErr Get4SampleInstruments(SndChannelPtr *sampleChan1, SndChannelPtr *sampleChan2,
  1119.                                 SndChannelPtr *sampleChan3, SndChannelPtr *sampleChan4,
  1120.                                 SndListHandle sndInstrument1, SndListHandle sndInstrument2,
  1121.                                 SndListHandle sndInstrument3, SndListHandle sndInstrument4)
  1122. {
  1123.     OSErr theErr;
  1124.  
  1125.     FreeAllChans();
  1126.  
  1127.     theErr = SndNewChannel(&(gChanInfo[0].chan), sampledSynth,
  1128.                             kInitNone, GetRoutineAddress(DoCallBack));
  1129.     if (theErr == noErr) {
  1130.         gChanInfo[0].chanType = sampledSynth;
  1131.         theErr = InstallSampleSnd(&gChanInfo[0], sndInstrument1);
  1132.         if (theErr == noErr) {
  1133.             theErr = SndNewChannel(&(gChanInfo[1].chan), sampledSynth,
  1134.                                     kInitNone, GetRoutineAddress(DoCallBack));
  1135.             if (theErr == noErr) {
  1136.                 gChanInfo[1].chanType = sampledSynth;
  1137.                 theErr = InstallSampleSnd(&gChanInfo[1], sndInstrument2);
  1138.                 if (theErr == noErr) {
  1139.                     theErr = SndNewChannel(&(gChanInfo[2].chan), sampledSynth,
  1140.                                             kInitNone, GetRoutineAddress(DoCallBack));
  1141.                     if (theErr == noErr) {
  1142.                         gChanInfo[2].chanType = sampledSynth;
  1143.                         theErr = InstallSampleSnd(&gChanInfo[2], sndInstrument3);
  1144.                         if (theErr == noErr) {
  1145.                             theErr = SndNewChannel(&(gChanInfo[3].chan), sampledSynth,
  1146.                                                     kInitNone, GetRoutineAddress(DoCallBack));
  1147.                             if (theErr == noErr) {
  1148.                                 gChanInfo[3].chanType = sampledSynth;
  1149.                                 theErr = InstallSampleSnd(&gChanInfo[3], sndInstrument4);
  1150.                             }
  1151.                         }
  1152.                     }
  1153.                 }
  1154.             }
  1155.         }
  1156.     }
  1157.  
  1158.     if (theErr == noErr)
  1159.     {
  1160.         *sampleChan1 = gChanInfo[0].chan;
  1161.         *sampleChan2 = gChanInfo[1].chan;
  1162.         *sampleChan3 = gChanInfo[2].chan;
  1163.         *sampleChan4 = gChanInfo[3].chan;
  1164.     }
  1165.     else
  1166.         FreeAllChans();
  1167.     return (theErr);
  1168. }
  1169.  
  1170. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1171. /*
  1172. Given a channel and pointer to a wave table, this will install the wave
  1173. for use as an instrument into the channel.  If I find the application
  1174. giving me a nil pointer, I'll return an error.  I use _SndDoImmediate
  1175. to get the sound installed because I don't want this to be queued.
  1176. */
  1177.  
  1178. #pragma segment SoundUnit
  1179. pascal OSErr InstallWave(SndChannelPtr waveChan, Ptr aWavePtr, short waveLength)
  1180. {
  1181.     SndCommand theCmd;
  1182.     OSErr      result;
  1183.  
  1184.     if (aWavePtr != nil) {
  1185.         theCmd.cmd = waveTableCmd;
  1186.         theCmd.param1 = waveLength;
  1187.         theCmd.param2 = (long)aWavePtr;
  1188.         result = SndDoImmediate(waveChan, &theCmd);
  1189.     }
  1190.     else
  1191.         result = memPCErr;                        // Pointer Check failed
  1192.     return (result);
  1193. }
  1194.  
  1195. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1196. /*
  1197. NewWaveChan creates a new wave table channel and sets myChan to point
  1198. to it.  If any error occurs, the error code is returned as a function
  1199. result.
  1200. */
  1201.  
  1202. #pragma segment SoundUnit
  1203. OSErr NewWaveChan(ChanInfoPtr info, short init)
  1204. {
  1205.     OSErr theErr;
  1206.  
  1207.     theErr = SndNewChannel(&(info->chan), waveTableSynth,
  1208.                             init, GetRoutineAddress(DoCallBack));
  1209.     if (theErr == noErr)
  1210.         info->chanType = waveTableSynth;
  1211.  
  1212.     return (theErr);
  1213. }
  1214.  
  1215. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1216. /*
  1217. This will return four wave table channels with their waves installed.
  1218. This must be done before calling ChanAvailable.  Otherwise that test will
  1219. fail.  If I cannot obtain all four wave channels I will dispose of the
  1220. ones I did get before returning the error.  This routine expects to find
  1221. four wave table pointers, or it will fail.
  1222.  
  1223. Versions 1.2: After calling NewWaveChan, we do not need to call ChanAvailable
  1224. since we know that Sound Manager 2 or later will not do the wrong thing.
  1225. */
  1226.  
  1227. #pragma segment SoundUnit
  1228. pascal OSErr GetWaveChans(SndChannelPtr *waveChan1, SndChannelPtr *waveChan2,
  1229.                             SndChannelPtr *waveChan3, SndChannelPtr *waveChan4)
  1230. {
  1231.     OSErr theErr;
  1232.  
  1233.     FreeAllChans();
  1234.  
  1235.     theErr = NewWaveChan(&gChanInfo[0], waveInitChannel0);
  1236.     if (theErr == noErr)
  1237.     {
  1238.         theErr = NewWaveChan(&gChanInfo[1], waveInitChannel1);
  1239.         if (theErr == noErr)
  1240.         {
  1241.             theErr = NewWaveChan(&gChanInfo[2], waveInitChannel2);
  1242.             if (theErr == noErr)
  1243.                 theErr = NewWaveChan(&gChanInfo[3], waveInitChannel3);
  1244.         }
  1245.     }
  1246.  
  1247.     if (theErr != noErr)
  1248.         FreeAllChans();                            // we didn't make it
  1249.     else {
  1250.         *waveChan1 = gChanInfo[0].chan;
  1251.         *waveChan2 = gChanInfo[1].chan;
  1252.         *waveChan3 = gChanInfo[2].chan;
  1253.         *waveChan4 = gChanInfo[3].chan;
  1254.     }
  1255.     return (theErr);
  1256. }
  1257.  
  1258. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1259. /*
  1260. This will create a channel for the square wave synthesizer.  There
  1261. are no INIT options used by this synthesizer, but I will set the timbre
  1262. to adjust the tone quality.
  1263.  
  1264. VERSION 1.2: We no longer need to call ChanAvailable since we're only
  1265. supporting Sound Manager 2.0 or later.
  1266. */
  1267.  
  1268. #pragma segment Sound
  1269. pascal OSErr GetSquareWaveChan(SndChannelPtr *squareChan, short timbre)
  1270. {
  1271.     OSErr theErr;
  1272.  
  1273.     FreeAllChans();
  1274.     theErr = SndNewChannel(&(gChanInfo[0].chan), squareWaveSynth,
  1275.                                 kInitNone, GetRoutineAddress(DoCallBack));
  1276.     if (theErr == noErr) {
  1277.         gChanInfo[0].chanType = squareWaveSynth;
  1278.         theErr = SetSquareWaveTimbre(gChanInfo[0].chan, timbre, !kWait);
  1279.     }
  1280.     if (theErr != noErr)
  1281.         FreeAllChans();
  1282.     *squareChan = gChanInfo[0].chan;
  1283.     return (theErr);
  1284. }
  1285.  
  1286. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1287. /*
  1288. This is the routine to create a channel that isn't associated with any
  1289. synthesizer.  Why? Because if you wanted to use _SndPlay asynchronously
  1290. you need to get such a channel.
  1291.  
  1292. BUG NOTE: Do not use a channel already associated to a snth with
  1293. _SndPlay.  This causes the Sound Manager to install a second copy of the
  1294. same snth.
  1295.  
  1296. VERSION 1.2: We no longer need to call ChanAvailable since we're only
  1297. supporting Sound Manager 2.0 or later.
  1298. */
  1299.  
  1300. #pragma segment SoundUnit
  1301. pascal OSErr GetNoSynthChan(SndChannelPtr *chan)
  1302. {
  1303.     OSErr theErr;
  1304.  
  1305.     FreeAllChans();
  1306.     theErr = SndNewChannel(&(gChanInfo[0].chan), kNoSynth,
  1307.                                 kInitNone, GetRoutineAddress(DoCallBack));
  1308.     if (theErr == noErr)
  1309.         gChanInfo[0].chanType = kNoSynth;
  1310.     if (theErr != noErr)
  1311.         FreeAllChans();
  1312.     *chan = gChanInfo[0].chan;
  1313.     return (theErr);
  1314. }
  1315.  
  1316. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1317. /*
  1318. This routine will use the given channel and snd resource with _SndPlay.
  1319. This is used to play a sound, which is a series of sound commands commonly
  1320. referred to as a sequence.  First thing I do is make sure the song fits in
  1321. memory.  _SndPlay will lock this resource in memory and then pump the snd
  1322. for all of its worth.  I am calling it asynchronously, and if I was using
  1323. a snd that contained sound data I wouldn't mark the snd as being
  1324. purgeable.  But in this case, _SndPlay will be done with the snd as soon
  1325. as it returns because it copied all of the commands into the channel.
  1326. (There's no data associated with a sequence, just commands.) _SndPlay
  1327. will not return until it has done so.  After _SndPlay I need to work
  1328. around a bug in the freqDurationCmd.  The last thing to do is to send a
  1329. callBackCmd to signal me that the channel has completed.  If any Sound
  1330. Manager errors are encountered, I return them to the application.  If the
  1331. application passed me a nil snd handle, I'll return an error.
  1332.  
  1333. WARNING: Make sure you are using a snd that only has note type commands
  1334. in it and not something such as a bufferCmd.
  1335.  
  1336. BUG NOTE: There is problem when the final sound command is a freqDurationCmd.
  1337. The note will continue to sound, looping forever, until a quietCmd is sent
  1338. or the channel is disposed of.  To prevent unwanted looping, I send a
  1339. quietCmd after all notes.  Also read a related bug note when disposing of
  1340. channels in the routine FreeChan().
  1341. */
  1342.  
  1343. #pragma segment SoundUnit
  1344. pascal OSErr PlaySong(SndChannelPtr chan, SndListHandle sndSong)
  1345. {
  1346.     OSErr theErr;
  1347.  
  1348.     theErr = SndDataAvailable(sndSong);                // get the data loaded
  1349.     if (theErr == noErr) {
  1350.         theErr = SndPlay(chan, sndSong, kSMAsynch);    // pump the sound
  1351.         HUnlock((Handle)sndSong);
  1352.         HPurge((Handle)sndSong);
  1353.         if (theErr == noErr) {
  1354.             theErr = SendQuiet(chan, kWait);        // work around bug
  1355.             if (theErr == noErr)
  1356.                 theErr = SoundComplete(chan);
  1357.         }
  1358.         else
  1359.             theErr = nilHandleErr;                    // snd data was not available
  1360.     }
  1361.     if (theErr != noErr)
  1362.         FreeAllChans();
  1363.     return (theErr);
  1364. }
  1365.  
  1366. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1367. /*
  1368. This is used to send a syncCmd to a channel and causes the other channels
  1369. that are being held by a synchCmd to be released.  Of course, this assumes
  1370. the application has already called SynchChans.  _SndDoImmediate is used
  1371. to get the command directly to the synthesizer bypassing the queue.
  1372.  
  1373. BUG NOTE: I've found that immediately clearing the channels and starting
  1374. new ones may cause the channels to startup playing out of synch?  This
  1375. happens while disposing the wave channels and starting them immediately.
  1376. */
  1377.  
  1378. #pragma segment SoundUnit
  1379. OSErr ReleaseSynch(SndChannelPtr chan)
  1380. {
  1381.     SndCommand theCmd;
  1382.  
  1383.     theCmd.cmd = syncCmd;
  1384.     theCmd.param1 = 1;
  1385.     theCmd.param2 = kSyncID;
  1386.     return (SndDoImmediate(chan, &theCmd));
  1387. }
  1388.  
  1389. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1390. /*
  1391. This is a utility routine for SynchChans.  It sends a syncCmd command
  1392. to the channel specified by chan using the count parameter specified
  1393. by count.  Synch1Chan returns whatever error that SndDoImmediate returns.
  1394. */
  1395.  
  1396. #pragma segment SoundUnit
  1397. OSErr Synch1Chan(SndChannelPtr chan, short count)
  1398. {
  1399.     SndCommand theCmd;
  1400.  
  1401.     theCmd.cmd = syncCmd;
  1402.     theCmd.param1 = count;
  1403.     theCmd.param2 = kSyncID;
  1404.     return (SndDoImmediate(chan, &theCmd));
  1405. }
  1406.  
  1407. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1408. /*
  1409. This is used to synchronize four wave table channels.  By first sending
  1410. the synchCmd, I can send a sequence of other commands to the channel and
  1411. not have the channel attempt to start processing any of them.  That is until
  1412. another synchCmd is sent causing all of the previous synchCmd's counter
  1413. to be decremented.  After getting all the channels in synch and sending
  1414. the sequence of further commands, then use the ReleaseSynch routine to
  1415. start all of the channels processing their respective queues.
  1416. _SndDoImmediate is used to get the command directly to the synthesizer
  1417. bypassing the queue.
  1418. */
  1419.  
  1420. #pragma segment SoundUnit
  1421. OSErr SynchChans(SndChannelPtr chan1, SndChannelPtr chan2,
  1422.                         SndChannelPtr chan3, SndChannelPtr chan4)
  1423. {
  1424.     OSErr theErr;
  1425.  
  1426.     theErr = Synch1Chan(chan4, 5);
  1427.     if (theErr == noErr) {
  1428.         theErr = Synch1Chan(chan3, 4);
  1429.         if (theErr == noErr) {
  1430.             theErr = Synch1Chan(chan2, 3);
  1431.             if (theErr == noErr)
  1432.                 theErr = Synch1Chan(chan1, 2);
  1433.         }
  1434.     }
  1435.     return (theErr);
  1436. }
  1437.  
  1438. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1439. /*
  1440. In order to synchronize channels, the synchCmd is needed.  Once all of the
  1441. song has been sent into each channel, a final synchCmd is issued to
  1442. release them.  Don't send more commands into a channel that it can hold at
  1443. one time while the channel is in synch mode.
  1444. */
  1445.  
  1446. #pragma segment SoundUnit
  1447. pascal OSErr Play4ChanSongs(SndChannelPtr chan1, SndChannelPtr chan2,
  1448.                             SndChannelPtr chan3, SndChannelPtr chan4,
  1449.                             SndListHandle song1, SndListHandle song2,
  1450.                             SndListHandle song3, SndListHandle song4)
  1451. {
  1452.     OSErr theErr;
  1453.  
  1454.     theErr = SynchChans(chan1, chan2, chan3, chan4);
  1455.     if (theErr == noErr) {
  1456.         theErr = PlaySong(chan1, song1);
  1457.         if (theErr == noErr) {
  1458.             theErr = PlaySong(chan2, song2);
  1459.             if (theErr == noErr) {
  1460.                 theErr = PlaySong(chan3, song3);
  1461.                 if (theErr == noErr) {
  1462.                     theErr = PlaySong(chan4, song4);
  1463.                     if (theErr == noErr)
  1464.                         theErr = ReleaseSynch(chan1);
  1465.                 }
  1466.             }
  1467.         }
  1468.     }
  1469.  
  1470.     return (theErr);
  1471. }
  1472.  
  1473. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1474. /*
  1475. WARNING: IT IS RECOMMENDED THAT YOU DO NOT USE THIS CODE.  I've provided
  1476. this routine because people have asked me how HyperCard performs its PLAY
  1477. command and why their HyperCard sounds do not sound right using the
  1478. _SndPlay routine.  The correct answer is that _SndPlay plays the sound
  1479. correctly.  HyperCard is attempting to change the frequency by adjusting
  1480. the sample rate.  This is NOT the correct approach.  Define the sound
  1481. buffer as it should be played.  _SndPlay plays the sound as it is defined.
  1482. If the result from _SndPlay is not what you want, then it is the sample
  1483. that is incorrect and should be edited.  The sample rate is the rate at
  1484. which the sound was recorded.  If you didn't record it, then how do you
  1485. know what's the correct rate?  Set the baseFrequency to the note that was
  1486. recorded.  If you recorded middle C at 22k, then the rate is 22k and the
  1487. baseFrequency is middle C.  HyperCard is incorrect in using the sample rate as
  1488. the frequency of the sound.  Furthermore, using this technique of
  1489. calculating a new sample rate can introduce errors.  The resulting sample
  1490. rate will not be the proper pitch.  Also, the sample rate for high pitches
  1491. will be very inaccurate and impossible for the Mac to reproduce.  Such a
  1492. problem can happen if the given sample rate was 22k and is to be played
  1493. back at three octaves higher.  Even 44k samples transposed up a half
  1494. octave will fail.  Using the soundCmd and freqDurationCmd will not have this
  1495. problem.
  1496.  
  1497. Given a sound resource, this routine will play it in the manner that
  1498. HyperCard does.  HyperCard assumes that a sound is to be played at middle
  1499. C when the user does not specify a note value in the PLAY command.  I
  1500. don't know why. (What's middle C when I want to hear speech or the sound
  1501. of crickets?) At any rate(pun intended), I get a sampled sound channel.
  1502. I find the sound data offset in the resource, which has to be locked down
  1503. at this time.  Once I have the sound data, I get its original sample rate.
  1504. I have to calculate what a new sample rate would be based on its baseFrequency.
  1505. The baseFrequency is the note at which the sound was recorded.  I'm not sure
  1506. what this means to crickets, but if this is set to middle C then HyperCard
  1507. doesn't attempt to modify the sample rate. (If you're wondering how the
  1508. math works in this routine, buy a book on music theory.  I'm here to
  1509. provide Mac support.) Once I've adjusted the sample rate, I use the
  1510. bufferCmd to play it.  Then I restore the sound resource to its original
  1511. state.  If I didn't do this it would be possible that the resource was
  1512. still in memory the next time I use it having the adjusted sample rate.
  1513. This would cause me to incorrectly adjust it again.  Unlike HyperCard, I
  1514. can do this for both a format 1 and 2.
  1515.  
  1516. BUG NOTE: Do not call SANE while the Sound Manager is running.  Refer to
  1517. Tech Note #235.
  1518.  
  1519. VERSION 1.1:  Replaced the previous test of the sound header's encode
  1520. value.  The previous version was incorrect.  The bufferCmd will automatically
  1521. de-code a MACE compressed sound if MACE is available.  Otherwise, the sound
  1522. will not be able to be used.  So, a new routine is being used to check for
  1523. supported sound headers.  The conflict with the Sound Manager and SANE was
  1524. resolved in the new Sound Manager.  The Sound Manager no longers uses extended
  1525. numbers at interrupt level, and instead uses fixed math.
  1526. */
  1527.  
  1528. #pragma segment SoundUnit
  1529. pascal OSErr HyperSndPlay(SndListHandle sndHandle)
  1530. {
  1531.     SndCommand      theCmd;
  1532.     OSErr              theErr;
  1533.     long             dataOffset;
  1534.     short             sndDataType;
  1535.     short              ignore;
  1536.     SoundHeaderPtr    dataPtr;
  1537.     short              thePower;
  1538.     double_t        newRate;
  1539.     Fixed           oldRate;
  1540.  
  1541.     theErr = HoldSnd(sndHandle);
  1542.     if (theErr == noErr) {
  1543.         theErr = GetSampleChan(&(gChanInfo[0].chan), kInitNone, nil);
  1544.         gChanInfo[0].dataHandle = sndHandle;                  // so FreeAllChans can dispose of data
  1545.         if (theErr == noErr) {
  1546.             dataOffset = GetSndDataOffset(sndHandle, &sndDataType, &ignore);
  1547.             if (sndDataType == sampledSynth) {
  1548.                 dataPtr = (SoundHeaderPtr)((long)*sndHandle + dataOffset);
  1549.  
  1550.                 if (SupportedSH(dataPtr)) {
  1551.                     oldRate = dataPtr->sampleRate;      // save original sample rate
  1552.                     if (dataPtr->baseFrequency != kMiddleC) {
  1553.                         if (dataPtr->sampleRate > (SHRT_MAX << 16)) // large positive number
  1554.                             newRate = Fix2X(dataPtr->sampleRate - (SHRT_MAX << 16))
  1555.                                             + (double_t)SHRT_MAX;
  1556.                         else
  1557.                             newRate = Fix2X(dataPtr->sampleRate);
  1558.                         thePower = (kMiddleC) - (dataPtr->baseFrequency);
  1559.                         dataPtr->sampleRate = X2Fix(newRate *
  1560.                                             pow((double_t)twelfthRootTwo, (double_t)thePower));
  1561.                     }
  1562.                     theCmd.cmd = bufferCmd;
  1563.                     theCmd.param1 = 0;
  1564.                     theCmd.param2 = (long)dataPtr;
  1565.                     theErr = SndDoImmediate(gChanInfo[0].chan, &theCmd);
  1566.                     if (theErr == noErr)
  1567.                         theErr = SoundComplete(gChanInfo[0].chan);
  1568.                     dataPtr->sampleRate = oldRate;      // restore original sample rate
  1569.                 } else
  1570.                     theErr = badFormat;                    //not SupportedSH
  1571.             } else
  1572.                 theErr = badFormat;                        //sndDataType not sampledSynth
  1573.         }
  1574.     }
  1575.     if (theErr != noErr)
  1576.         FreeAllChans();
  1577.     return (theErr);
  1578. }
  1579.  
  1580. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1581. /*
  1582. Given a sound resource, this routine will call _SndPlay.  The snd must
  1583. be either a format 2 or format 1 that contains snth information.
  1584. Using _SndPlay asynchronously requires us to lock the snd prior to
  1585. calling the trap.  The reason being is _SndPlay calls HSetState as soon
  1586. as the trap exits, and restores the handle to whatever is was just before
  1587. the call.  This would be bad when using the sound asynchronously.  If the
  1588. sound being passed in happens to be a compressed sound created with MACE,
  1589. it will "do the right thing."  If MACE isn't around the Sound Manager
  1590. will pretend to play a sound but nothing will be heard.
  1591.  
  1592. BUG NOTE:  The sampled sound synthesizer in System 6.0x does not check for
  1593. a Memory Manager error when allocating its internal buffer.  There is a
  1594. call to NewPtr(1316)and if a nil is return, the Sound Manager will write
  1595. randomly to memory.  Also, the pointer is allocated into the application's
  1596. heap instead of the system's.
  1597.  
  1598. BUG NOTE:  _SndPlay when using System 6.0.4 and a sampled sound will send
  1599. a bogus callBackCmd into the channel.  This will cause the user's call
  1600. back procedure to be called as soon as the sound has completed.  Refer
  1601. to the DoCallBack routine for details.
  1602.  
  1603. VERSION 1.1:  Add the check for the sound header being supported and
  1604. replaced the check of the snth information.  No synth information in the
  1605. snd is valid and would mean to play the snd using the squareWaveSynth.
  1606. */
  1607.  
  1608. #pragma segment SoundUnit
  1609. pascal OSErr AsynchSndPlay(SndListHandle sndHandle)
  1610. {
  1611.     SoundHeaderPtr dataPtr;
  1612.     OSErr theErr;
  1613.     long dataOffset;
  1614.     short sndDataType;
  1615.     short ignore;
  1616.  
  1617.     theErr = HoldSnd(sndHandle);                //hold on to the sound
  1618.     if (theErr == noErr) {
  1619.         theErr = GetNoSynthChan(&(gChanInfo[0].chan));
  1620.         gChanInfo[0].dataHandle = sndHandle;        //so FreeAllChans can dispose of data
  1621.         if (theErr == noErr) {
  1622.             gChanInfo[0].chanType = GetSynthInfo(sndHandle).modNumber;
  1623.             dataOffset = GetSndDataOffset(sndHandle, &sndDataType, &ignore);
  1624.             if (sndDataType == sampledSynth) {
  1625.                 dataPtr = (SoundHeaderPtr)((long)*sndHandle + dataOffset);
  1626.                 if ( !(SupportedSH(dataPtr)) )
  1627.                     theErr = badFormat;
  1628.             }
  1629.             if (theErr == noErr) {
  1630.                 theErr = SndPlay(gChanInfo[0].chan, sndHandle, kSMAsynch);
  1631.                 if (theErr == noErr)
  1632.                     theErr = SoundComplete(gChanInfo[0].chan);
  1633.             }
  1634.         }
  1635.     }
  1636.     if (theErr != noErr)
  1637.         FreeAllChans();
  1638.     return(theErr);
  1639. }
  1640.  
  1641.